package com.seuic.socketassistant.viewmodel

import android.app.Application
import android.content.Context
import android.util.Log
import androidx.core.content.edit
import androidx.databinding.ObservableArrayList
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import androidx.preference.PreferenceManager
import com.blankj.utilcode.util.DeviceUtils
import com.blankj.utilcode.util.NetworkUtils
import com.blankj.utilcode.util.ToastUtils
import com.hoho.android.usbserial.driver.UsbSerialDriver
import com.letter.serialport.SerialPortFinder
import com.seuic.socketassistant.R
import com.seuic.socketassistant.local.ConnectionParamDao
import com.seuic.socketassistant.local.MessageDao
import com.seuic.socketassistant.connection.*
import com.seuic.socketassistant.utils.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.nio.charset.Charset

private const val CONNECT_PARAM_SP = "connection"
private const val SP_REMOTE_IP = "remote_ip"
private const val SP_REMOTE_PORT = "remote_port"
private const val SP_LOCAL_PORT = "local_port"

/**
 * Main view model
 * @property title MutableLiveData<String> 界面标题
 * @property userName MutableLiveData<String> 用户名
 * @property localIp MutableLiveData<String> 本地Ip
 * @property inputText MutableLiveData<String> 输入内容
 * @property messageList ObservableList<MessageDao> 消息列表
 * @property connectionParamDao MutableLiveData<ConnectionDao> 连接配置参数
 * @property connectionList MutableLiveData<MutableList<AbstractConnection>> 连接列表
 * @property selectedConnectionIndex MutableLiveData<Int> 选中的连接索引
 *
 * @author KP(zhn2174@gmail.com)
 * @since 1.0.0
 */
class MainViewModel(application: Application) : AndroidViewModel(application) {

    companion object {
        private const val TAG = "MainViewModel"
        val CHARSET_LIST = listOf("utf-8", "gbk", "unicode")
    }

    val title = MutableLiveData("SocketAssistant")
    val userName by lazy {
        MutableLiveData(DeviceUtils.getModel())
    }
    val localIp = MutableLiveData("")
    val inputText = MutableLiveData("")
    val messageList = ObservableArrayList<MessageDao>()
    val connectionParamDao by lazy {
        val connectionParamDao = ConnectionParamDao()
        val sp = application.getSharedPreferences(CONNECT_PARAM_SP, Context.MODE_PRIVATE)
        connectionParamDao.netConnectionParam.apply {
            remoteIp = sp.getString(SP_REMOTE_IP, null)
            remotePort = sp.getString(SP_REMOTE_PORT, null)
            localPort = sp.getString(SP_LOCAL_PORT, null)
        }
        MutableLiveData(connectionParamDao)
    }
    private val connectionList by lazy {
        MutableLiveData<MutableList<AbstractConnection>>(mutableListOf())
    }
    private lateinit var usbSerialDrivers: List<UsbSerialDriver>
    val serialPortList = mutableListOf<String>()
    val serialParityList = listOf("NONE", "ODD", "EVEN")
    val selectedConnectionIndex = MutableLiveData(-1)
    val hexTransmit = MutableLiveData(false)
    val hexReceive = MutableLiveData(false)
    val clearWhenSend = MutableLiveData(true)
    val lineWrap = MutableLiveData(false)
    val charSet = MutableLiveData(CHARSET_LIST[0])

    init {
        viewModelScope.launch {
            localIp.value = getString(R.string.main_activity_local_ip).format(getLocalIp())
        }
        selectedConnectionIndex.observeForever {
            title.value =
                if (it >= 0)
                    connectionList.value?.get(it)?.name
                else
                    getString(R.string.app_name)
        }
    }

    /**
     * 刷新串口列表
     */
    fun refreshSerialPort() {
        usbSerialDrivers = UsbSerialConnection.getDrivers(getContext())
        serialPortList.clear()
        serialPortList.addAll(SerialPortFinder().deviceNameList)
        for (device in usbSerialDrivers) {
            serialPortList.add("%s(%s)".format(device.device.productName, device.device.deviceName))
        }
    }

    /**
     * 发送消息
     */
    fun sendMessage() {
        if (inputText.value != null && inputText.value?.isNotEmpty()!!) {
            val connection = if (connectionList.value?.size ?: 0 > 0)
                connectionList.value?.get(selectedConnectionIndex.value ?: 0) else null
            viewModelScope.launch {
                withContext(Dispatchers.IO) {
                    try {
                        var text = inputText.value!!
                        if (lineWrap.value!!) {
                            text += if (hexTransmit.value!!) " 0D 0A" else "\r\n"
                        }
                        connection?.send(
                            if (hexTransmit.value != true)
                                text.toByteArray(Charset.forName(charSet.value))
                            else
                                text.toHexByteArray()
                        )
                    } catch (e: Exception) {
                        Log.e(TAG, "", e)
                    }
                }
            }
            messageList.add(
                MessageDao(
                    MessageDao.Type.SEND,
                    inputText.value,
                    connection?.name
                )
            )
            if (clearWhenSend.value == true) {
                inputText.value = null
            }
        }
    }

    /**
     * 获取本地IP地址
     * @return (kotlin.String..kotlin.String?) Ip地址
     */
    private suspend fun getLocalIp() = withContext(Dispatchers.IO) {
        return@withContext when (NetworkUtils.getNetworkType()) {
            NetworkUtils.NetworkType.NETWORK_WIFI -> NetworkUtils.getIpAddressByWifi()
            else -> NetworkUtils.getIPAddress(true)
        }
    }

    /**
     * 建立连接
     */
    fun connect() {
        Log.d(TAG, "param: ${connectionParamDao.value?.serialConnectionParam}")
        viewModelScope.launch(Dispatchers.IO) {
            val sp = getContext().getSharedPreferences(CONNECT_PARAM_SP, Context.MODE_PRIVATE)
            sp.edit(commit = true) {
                putString(SP_REMOTE_IP, connectionParamDao.value?.netConnectionParam?.remoteIp)
                putString(SP_REMOTE_PORT, connectionParamDao.value?.netConnectionParam?.remotePort)
                putString(SP_LOCAL_PORT, connectionParamDao.value?.netConnectionParam?.localPort)
            }
            var connection: AbstractConnection? = null
            try {
                when (connectionParamDao.value?.type) {
                    ConnectionParamDao.Type.TCP_SERVER -> {
                        connection = TcpServerConnection(
                            connectionParamDao.value?.netConnectionParam?.localPort?.toInt() ?: 0
                        )
                    }
                    ConnectionParamDao.Type.TCP_CLIENT -> {
                        connection = TcpClientConnection(
                            connectionParamDao.value?.netConnectionParam?.remoteIp,
                            connectionParamDao.value?.netConnectionParam?.remotePort?.toInt() ?: 0,
                            maxPacketLen = getMaxPackageLen()
                        ).takeIf { it.isConnectViable() }
                    }
                    ConnectionParamDao.Type.UDP -> {
                        connection = UdpConnection(
                            connectionParamDao.value?.netConnectionParam?.remoteIp,
                            connectionParamDao.value?.netConnectionParam?.remotePort?.toInt() ?: 0,
                            connectionParamDao.value?.netConnectionParam?.localPort?.toInt() ?: 0,
                            maxPacketLen = getMaxPackageLen()
                        )
                    }
                    ConnectionParamDao.Type.SERIAL -> {
                        val portName = connectionParamDao.value?.serialConnectionParam?.port
                        if (portName != null) {
                            for (port in SerialPortFinder().deviceNameList) {
                                if (portName == port) {
                                    connection = SerialConnection(
                                        portName,
                                        connectionParamDao.value?.serialConnectionParam?.baudRate?.toInt()
                                            ?: 0,
                                        connectionParamDao.value?.serialConnectionParam?.dataBits?.toInt()
                                            ?: 0,
                                        parserParity(connectionParamDao.value?.serialConnectionParam?.parity),
                                        connectionParamDao.value?.serialConnectionParam?.stopBits?.toInt()
                                            ?: 0,
                                        maxPacketLen = getMaxPackageLen()
                                    )
                                    break
                                }
                            }
                            for (driver in usbSerialDrivers) {
                                if (getUsbSerialPort(portName) == driver.device.deviceName) {
                                    connection = UsbSerialConnection(
                                        getContext(),
                                        driver,
                                        connectionParamDao.value?.serialConnectionParam?.baudRate?.toInt()
                                            ?: 0,
                                        connectionParamDao.value?.serialConnectionParam?.dataBits?.toInt()
                                            ?: 0,
                                        parserParity(connectionParamDao.value?.serialConnectionParam?.parity),
                                        connectionParamDao.value?.serialConnectionParam?.stopBits?.toInt()
                                            ?: 0,
                                        maxPacketLen = getMaxPackageLen()
                                    )
                                }
                                break
                            }
                        }
                    }
                }
            } catch (e: Exception) {
                withContext(Dispatchers.Main) {
                    toast(R.string.main_view_model_conect_fail)
                }
                return@launch
            }
            connection?.apply {
                onReceivedListener = onConnectionPacketReceived
                onConnectedListener = onConnectionConnected
                onDisConnectedListener = onConnectionDisconnected
                connect()
            } ?: kotlin.run {
                ToastUtils.showShort("连接无法访问")
            }
        }
    }

    /**
     * 断开连接
     * @param index Int 连接索引
     */
    fun disconnect(index: Int = selectedConnectionIndex.value ?: 0) {
        if (index < 0) {
            toast(R.string.main_activity_toast_no_connection)
            return
        }
        viewModelScope.launch {
            withContext(Dispatchers.IO) {
                if (index < connectionList.value?.size ?: 0) {
                    connectionList.value?.get(index)?.disconnect()
                }
            }
        }
    }

    /**
     * 获取连接名字列表
     * @return List<String> 列表
     */
    fun getConnectionNameList() = List(connectionList.value?.size ?: 0) {
        connectionList.value?.get(it)?.name ?: ""
    }

    /**
     * 连接成功回调
     */
    private val onConnectionConnected: (AbstractConnection) -> Unit = {
        viewModelScope.launch {
            withContext(Dispatchers.Main) {
                connectionList.value?.add(it)
                toast(getString(R.string.main_activity_toast_connection_connect).format(it.name))
                if (selectedConnectionIndex.value == -1) {
                    selectedConnectionIndex.value = 0
                }
            }
        }
    }

    /**
     * 连接断开回调
     */
    private val onConnectionDisconnected: (AbstractConnection) -> Unit = {
        viewModelScope.launch {
            withContext(Dispatchers.Main) {
                if (selectedConnectionIndex.value ?: -1 == (connectionList.value?.size ?: 0) - 1) {
                    selectedConnectionIndex.value = (selectedConnectionIndex.value ?: 0) - 1
                }
                toast(getString(R.string.main_activity_toast_connection_disconnect).format(it.name))
                connectionList.value?.remove(it)
            }
        }
    }

    /**
     * 连接数据接收回调
     */
    private val onConnectionPacketReceived: (AbstractConnection, ByteArray) -> Unit =
        { abstractConnection, bytes ->
            viewModelScope.launch {
                withContext(Dispatchers.Main) {
                    messageList.add(
                        MessageDao(
                            MessageDao.Type.RECEIVED,
                            if (hexReceive.value != true)
                                bytes.toString(Charset.forName(charSet.value))
                            else
                                bytes.toHexString(),
                            abstractConnection.name
                        )
                    )
                }
            }
        }

    /**
     * 获取最大包长
     * @return Int 包长
     */
    private fun getMaxPackageLen() =
        PreferenceManager
            .getDefaultSharedPreferences(getContext())
            .getString("max_package_len", "1024")?.toInt() ?: 1024

}

/**
 * 解析串口校验参数
 * @param parity String 参数
 * @return Int 数值
 */
private fun parserParity(parity: String?) = when (parity) {
    "ODD" -> 1
    "EVEN" -> 2
    else -> 0
}

/**
 * 获取USB串口端口名
 * @param data String 数据
 * @return String 端口名
 */
private fun getUsbSerialPort(data: String): String? {
    val clips = data.split(Regex("[()]"))
    clips.forEach {
        Log.d("MainViewModel", "clip: $it")
    }
    return if (clips.size > 2) clips[clips.size - 2] else null
}